﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml.Serialization;
using System.Diagnostics.CodeAnalysis;

namespace Shared.Utilities
{
    public static class Serialization
    {
        #region Private Fields

        private static XmlSerializerFactory serializerFactory = new XmlSerializerFactory();

        #endregion

        /// <summary>
        /// Serializes the specified object.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <returns></returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter"),
         SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames")]
        public static string Serialize<T>(object obj)
        {
            //Guard.ArgumentNotNull(obj, "obj");
            return Serialize<T>(obj, null);
        }

        /// <summary>
        /// Serializes the specified obj.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj">The obj.</param>
        /// <param name="types">The types.</param>
        /// <returns></returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter"),
         SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames")]
        public static string Serialize<T>(object obj, ICollection<Type> types)
        {
            return Serialize(obj, typeof(T), types, Encoding.UTF8);
        }

        /// <summary>
        /// Serializes the specified object.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj">The object.</param>
        /// <param name="types">The types.</param>
        /// <param name="encoding">The encoding.</param>
        /// <returns></returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter"),
         SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames")]
        public static string Serialize<T>(object obj, ICollection<Type> types, Encoding encoding)
        {
            return Serialize(obj, typeof(T), types, encoding);
        }

        /// <summary>
        /// Serializes the specified object.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj">The object.</param>
        /// <param name="objType">The type of the object to serailize.</param>
        /// <param name="types">The types.</param>
        /// <param name="encoding">The encoding.</param>
        /// <returns></returns>
        [SuppressMessage("Microsoft.Naming", "CA1720:IdentifiersShouldNotContainTypeNames")]
        public static string Serialize(object obj, Type objType, ICollection<Type> types, Encoding encoding)
        {
            //Guard.ArgumentNotNull(obj, "obj");
            string text;

            try
            {
                XmlSerializer serializer = GetXmlSerializer(objType, types);

                using (MemoryStream stream = new MemoryStream())
                {
                    serializer.Serialize(stream, obj);
                    text = encoding.GetString(stream.ToArray());
                }
            }
            catch (InvalidOperationException ioe)
            {
                //Trace.TraceWarning(Microsoft.Practices.Modeling.Common.Properties.Resources.XmlSerializationFailedSwitchToBinary);
                //Trace.TraceWarning(ioe.InnerException == null ? ioe.Message : ioe.InnerException.Message);
                // if we could not use XML serialization, fallback to binary serialization
                BinaryFormatter formatter = new BinaryFormatter();
                using (MemoryStream stream = new MemoryStream())
                {
                    formatter.Serialize(stream, obj);
                    text = Convert.ToBase64String(stream.ToArray());
                }
            }

            return text;
        }

        public static string SerializeAnObject(object obj)
        {
            System.Xml.
            XmlDocument doc = new XmlDocument();
            System.Xml.Serialization.
            XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(obj.GetType());
            System.IO.
            MemoryStream stream = new System.IO.MemoryStream();
            try
            {
                serializer.Serialize(stream, obj);
                stream.Position = 0;
                doc.Load(stream);
                return doc.InnerXml;
            }
            catch
            {
                throw;
            }
            finally
            {
                stream.Close();
                stream.Dispose();
            }
        }

        /// <summary>
        /// creates wcf message
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public static XmlReader CreateMessage(object obj)
        {
            string xmlOfAnObject = SerializeAnObject(obj);

            StringReader read = new StringReader(xmlOfAnObject);
            XmlReader reader = new XmlTextReader(read);
            return (reader);
        }

        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public static T Deserialize<T>(string data)
        {
            //Guard.ArgumentNotNullOrEmptyString(data, "data");

            return Deserialize<T>(data, null);
        }
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public static T Deserialize<T>(string data, ICollection<Type> types)
        {
            return Deserialize<T>(data, types, Encoding.UTF8);
        }

        /// <summary>
        /// Deserializes the specified types.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="types">The types.</param>
        /// <param name="data">The string representation of the type.</param>
        /// <param name="encoding">The encoding.</param>
        /// <returns></returns>
        [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public static T Deserialize<T>(string data, ICollection<Type> types, Encoding encoding)
        {
            //Guard.ArgumentNotNullOrEmptyString(data, "data");

            if (Uri.IsHexDigit(data[0]))
            {
                // we have a binary formatter base64 encoded
                BinaryFormatter formatter = new BinaryFormatter();
                using (MemoryStream stream = new MemoryStream(Convert.FromBase64String(data)))
                {
                    return (T)formatter.Deserialize(stream);
                }
            }

            // we have an xml serialized string
            XmlSerializer serializer = GetXmlSerializer(typeof(T), types);
            using (MemoryStream stream = new MemoryStream(encoding.GetBytes(data)))
            {
                return (T)serializer.Deserialize(stream);
            }
        }

        /// <summary>
        /// Function to get object from byte array
        /// </summary>
        /// <param name="_ByteArray">byte array to get object</param>
        /// <returns>object</returns>
        public static object ByteArrayToObject(byte[] _ByteArray)
        {
            try
            {
                // convert byte array to memory stream
                System.IO.MemoryStream _MemoryStream = new System.IO.MemoryStream(_ByteArray);

                // create new BinaryFormatter
                System.Runtime.Serialization.Formatters.Binary.BinaryFormatter _BinaryFormatter
                            = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();

                // set memory stream position to starting point
                _MemoryStream.Position = 0;

                // Deserializes a stream into an object graph and return as a object.
                return _BinaryFormatter.Deserialize(_MemoryStream);
            }
            catch (Exception _Exception)
            {
                // Error
                Console.WriteLine("Exception caught deserializing object from byte array: {0}", _Exception.ToString());
            }

            // Error occured, return null
            return null;
        }


        public static XmlSerializer CreateSerializer(Type type)
        {
            // try using cache first, otherwise fallback to new instance.
            return serializerFactory.CreateSerializer(type) ?? new XmlSerializer(type);
        }

        private static XmlSerializer GetXmlSerializer(Type type, ICollection<Type> extraTypes)
        {
            if (extraTypes == null ||
                extraTypes.Count == 0)
            {
                return CreateSerializer(type);
            }

            List<Type> etypes = new List<Type>(extraTypes);
            StringBuilder b = new StringBuilder();
            etypes.ForEach(t => b.Append(t.FullName));

            return new XmlSerializer(type, etypes.ToArray());

            //return GlobalCache.AddOrGetExisting<XmlSerializer>(b.ToString(), k =>
            //{
            //    return new XmlSerializer(type, etypes.ToArray());
            //});
        }

        //// ///<summary>
        //// ///DeSerialize an object
        //// ///</summary>
        //// ///<param name="xmlOfAnObject"></param>
        //// ///<returns></returns>
        //public static object DeSerializeAnObject(string xmlOfAnObject, Type type)
        //{
            
        //    StringReader read = new StringReader(xmlOfAnObject);
        //    System.Xml.Serialization.
        //    XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(type);
        //    System.Xml.
        //    XmlReader reader = new XmlTextReader(read);
        //    try
        //    {
        //        myObject = (
        //        MyClass)serializer.Deserialize(reader);
        //        return myObject;
        //    }
        //    catch
        //    {
        //        throw;
        //    }
        //    finally
        //    {
        //        reader.Close();
        //        read.Close();
        //        read.Dispose();
        //    }
        //}
    }
}
